Skip to content

Avoid unnecessary allocation in Uncancelable trait.#4600

Merged
djspiewak merged 1 commit into
typelevel:series/3.xfrom
nmichael44:optimizeAllocationsInUncancelableTrait
Jun 21, 2026
Merged

Avoid unnecessary allocation in Uncancelable trait.#4600
djspiewak merged 1 commit into
typelevel:series/3.xfrom
nmichael44:optimizeAllocationsInUncancelableTrait

Conversation

@nmichael44

Copy link
Copy Markdown
Contributor

Avoid unnecessary allocation in Uncancelable trait for idPoll. We can just allocate it once in object MonadCancel and apply the usual Id Monad trick (as in for example Resource.liftK).

@nmichael44

nmichael44 commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

This fails one of the tests with:

[error] cats-effect-kernel: Failed binary compatibility check against org.typelevel:cats-effect-kernel_3:3.4.10 (e:info.apiURL=https://typelevel.org/cats-effect/api/3.x/, e:info.versionScheme=early-semver)! Found 1 potential problems
[error]  * synthetic static method $init$(cats.effect.kernel.MonadCancel#Uncancelable)Unit in interface cats.effect.kernel.MonadCancel#Uncancelable does not have a correspondent in current version
[error]    filter with: ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.kernel.MonadCancel#Uncancelable.$init$")
[error] java.lang.RuntimeException: Failed binary compatibility check against org.typelevel:cats-effect-kernel_3:3.4.10 (e:info.apiURL=https://typelevel.org/cats-effect/api/3.x/, e:info.versionScheme=early-semver)! Found 1 potential problems

This happens since there are no longer any fields in the trait and so the Scala compiler eliminates the $init$ method completely. So the new trait is missing a method, and that's why the test complains that the old and new trait definitions are incompatible.

Please advice what the right thing to do is in this case.

@durban

durban commented May 19, 2026

Copy link
Copy Markdown
Contributor

I fear this is probably a valid complaint by mima. I suspect every existing implementer of MonadCancel#Uncancelable calls this $init$, so if it's missing, that's a problem. I'm not sure this minor optimization is worth it, if it causes a bincompat issue. (Although it is possible that there is some trick which would avoid the incompatibility...)

@nmichael44 nmichael44 force-pushed the optimizeAllocationsInUncancelableTrait branch 5 times, most recently from 290717f to 6b27a6c Compare June 6, 2026 09:42
@nmichael44

Copy link
Copy Markdown
Contributor Author

@durban Hi Daniel, I found a workaround to the mima problem. Please take a look and let me know when you get a chance.

@nmichael44 nmichael44 force-pushed the optimizeAllocationsInUncancelableTrait branch from 6b27a6c to dcba7ab Compare June 6, 2026 10:29
@mergify

mergify Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@djspiewak djspiewak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems promising but can we sanity check that the locally { () } doesn't reintroduce the very same allocation you're trying to eliminate? I don't know the encoding off hand.

@nmichael44

nmichael44 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

@djspiewak @durban Hi Daniel, below is the generated bytecode for the trait in question. As you can see there are no allocations. We just call the locally function giving it the pre-existing (from Predef$) Boxed Unit. locally is the identity function so it just returns what we give it, and $init$ just pops it off the stack.

public static void $init$(cats.effect.kernel.MonadCancel$Uncancelable);
    Code:
       0: getstatic     #86                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: getstatic     #55                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
       6: invokevirtual #89                 // Method scala/Predef$.locally:(Ljava/lang/Object;)Ljava/lang/Object;
       9: pop
      10: return
}

Here is locally from object Predef:

  @inline def locally[T](@deprecatedName("x") x: T): T = x

I am including the bytecode for the whole Uncancelable trait for completeness:

public interface cats.effect.kernel.MonadCancel$Uncancelable<F, E> {
  public static cats.effect.kernel.CancelScope rootCancelScope$(cats.effect.kernel.MonadCancel$Uncancelable);
    Code:
       0: aload_0
       1: invokespecial #21                 // InterfaceMethod rootCancelScope:()Lcats/effect/kernel/CancelScope;
       4: areturn

  public default cats.effect.kernel.CancelScope rootCancelScope();
    Code:
       0: getstatic     #26                 // Field cats/effect/kernel/CancelScope$Uncancelable$.MODULE$:Lcats/effect/kernel/CancelScope$Uncancelable$;
       3: areturn

  public static java.lang.Object canceled$(cats.effect.kernel.MonadCancel$Uncancelable);
    Code:
       0: aload_0
       1: invokespecial #33                 // InterfaceMethod canceled:()Ljava/lang/Object;
       4: areturn

  public default F canceled();
    Code:
       0: aload_0
       1: checkcast     #36                 // class cats/Applicative
       4: invokeinterface #39,  1           // InterfaceMethod cats/Applicative.unit:()Ljava/lang/Object;
       9: areturn

  public static java.lang.Object onCancel$(cats.effect.kernel.MonadCancel$Uncancelable, java.lang.Object, java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: aload_2
       3: invokespecial #47                 // InterfaceMethod onCancel:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
       6: areturn

  public default <A> F onCancel(F, F);
    Code:
       0: aload_2
       1: astore        5
       3: getstatic     #55                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
       6: pop
       7: goto          10
      10: getstatic     #55                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
      13: astore        4
      15: aload_1
      16: areturn

  public static java.lang.Object uncancelable$(cats.effect.kernel.MonadCancel$Uncancelable, scala.Function1);
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #62                 // InterfaceMethod uncancelable:(Lscala/Function1;)Ljava/lang/Object;
       5: areturn

  public default <A> F uncancelable(scala.Function1<cats.effect.kernel.Poll<F>, F>);
    Code:
       0: aload_1
       1: getstatic     #69                 // Field cats/effect/kernel/MonadCancel$.MODULE$:Lcats/effect/kernel/MonadCancel$;
       4: invokevirtual #73                 // Method cats/effect/kernel/MonadCancel$.cats$effect$kernel$MonadCancel$$cachedPoll:()Lcats/effect/kernel/Poll;
       7: invokeinterface #79,  2           // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      12: areturn

  public static void $init$(cats.effect.kernel.MonadCancel$Uncancelable);
    Code:
       0: getstatic     #86                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: getstatic     #55                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
       6: invokevirtual #89                 // Method scala/Predef$.locally:(Ljava/lang/Object;)Ljava/lang/Object;
       9: pop
      10: return
}

@djspiewak djspiewak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

@djspiewak djspiewak merged commit 045953e into typelevel:series/3.x Jun 21, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants